# coding: utf-8

import bz2
import zlib
import glob
import math
from operator import concat



def compress_bz2(sequence):
    return bz2.compress(reduce(concat, sequence), 9)
    
def compress_zlib(sequence):
    return zlib.compress(reduce(concat, sequence), 9)
    
    
    
def read_files(filenames):
    # this function accepts a list of filenames
    # or a pathname pattern string
    if type(filenames) == str:
        filenames = glob.glob(filenames)
        
    return [open(fn).read() for fn in filenames]



class Table(object):
    def __init__(self, sequence, compress, winsize=1):
        self.sequence = sequence
        self.compress = compress
        self.winsize = winsize
        
        self.table = {}
        for w in xrange(1, winsize+1):
            for i in xrange(len(sequence)):
                s = tuple(sequence[i:i+w])
                self.table[s] = float(len(compress(s)))
                
        self.means = {}
        for w in xrange(1, winsize+1):
            l = [self.table[k] for k in self.table if len(k) == w]
            self.means[w] = sum(l)/len(l)
            
        self.stddevs = {}
        for w in xrange(1, winsize+1):
            l = [(self[k]-self.means[w])**2 for k in self.table if len(k) == w]
            self.stddevs[w] = math.sqrt(sum(l)/len(l))
                
    def __getitem__(self, key):
        if type(key) == list:
            key = tuple(key)
        elif type(key) != tuple:
            key = (key,)
        return self.table[key]
        
    def mean(self, order):
        return self.means[order]
        
    def stddev(self, order):
        return self.stddevs[order]
    
    def ncd(self, x, y):
        Cxy = float(self.table[(x,y)])
        Cx = float(self.table[(x,)])
        Cy = float(self.table[(y,)])
        return (Cxy - min(Cx, Cy)) / max(Cx, Cy)



class VotingExperts(object):
    def __init__(self, sequence, winsize, compress):
        self.sequence = sequence
        self.winsize = winsize
        self.table = Table(sequence, compress, winsize)
        
    def expert1(self, *elems):
        mean = self.table.mean(len(elems))
        stddev = self.table.stddev(len(elems))
        return (self.table[elems] - mean) / stddev
        
    def expert2(self, *elems):
        return self.table[elems] - self.table[elems[:-1]]
        
    def vote(self):
        ws = self.winsize
        seq = self.sequence
        votes = [0] * (len(seq)-1)
                
        for i in xrange(len(seq)):
            win = seq[i:i+ws]
            if len(win) >= 2:
                score1 = [0] * (len(win)-1)
                score2 = [0] * (len(win)-1)
                for j in xrange(2, len(win)+1):
                    score1[j-2] = self.expert1(*win[:j])
                    score2[j-2] = self.expert2(*win[:j])
                if i < len(votes)-1:
                    votes[i+score1.index(max(score1))] += 1
                    votes[i+score2.index(max(score2))] += 1
        return votes
        
    def threshold(self, th):
        return [(1 if x > th else 0) for x in self.vote()]

    def segment(self, th):
        threshold = self.threshold(th)
        start = 0
        retval = []
        for i,v in enumerate(threshold):
            if v:
                retval.append(self.sequence[start:i+1])
                start = i + 1
        retval.append(self.sequence[start:])
        return retval
        
    def ncd(self):
        # see The similarity metric, Li et al. 
        retval = []
        for i in xrange(1, len(self.sequence)):
            retval.append(self.table.ncd(self.sequence[i-1], self.sequence[i]))
        return retval

